package com.aabte.nettyleran.netty.privateprotocolstack;

import com.aabte.nettyleran.util.MarshallingCodecFactory;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import org.jboss.marshalling.ByteInput;
import org.jboss.marshalling.Unmarshaller;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Daniel
 * @version 1.0
 * @date 2020/3/15
 */
public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder {
    private MarshallingDecoder marshallingDecoder;

    public NettyMessageDecoder(int maxFrameLength,
                               int lengthFieldOffset, int lengthFieldLength,
                               int lengthAdjustment, int initialBytesToStrip) throws IOException {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
        this.marshallingDecoder = new MarshallingDecoder();
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = (ByteBuf) super.decode(ctx, in);
        if (null == frame) {
            return null;
        }

        Header header = Header.builder()
                .crcCode(frame.readInt())
                .length(frame.readInt())
                .sessionId(frame.readLong())
                .type(frame.readByte())
                .priority(frame.readByte())
                .attachment(new HashMap<>(0))
                .build();

        int size = frame.readInt();

        if (size > 0) {
            Map<String, Object> attch = new HashMap<>(size);
            int keySize = 0;
            String key = null;
            byte[] keyArray = null;
            for (int i = 0; i < size; i++) {
                keySize = frame.readInt();
                keyArray = new byte[keySize];
                frame.readBytes(keyArray);
                key = new String(keyArray, StandardCharsets.UTF_8.toString());
                attch.put(key, this.marshallingDecoder.decode(frame));
            }

            keyArray = null;
            key = null;
            header.setAttachment(attch);
        }

        Object body = null;
        if (frame.readableBytes() > 4) {
            body = this.marshallingDecoder.decode(frame);
        }

        return NettyMessage.builder()
                .header(header)
                .body(body)
                .build();
    }

    private static class MarshallingDecoder {
        private Unmarshaller unmarshaller;

        public MarshallingDecoder() throws IOException {
            this.unmarshaller = MarshallingCodecFactory.buildUnMarshalling();
        }

        protected Object decode(ByteBuf in) throws IOException, ClassNotFoundException {
            int objectSize = in.readInt();
            ByteBuf buf = in.slice(in.readerIndex(), objectSize);
            ByteInput input = new ChannelBufferByteInput(buf);

            try {
                unmarshaller.start(input);
                Object obj = unmarshaller.readObject();
                unmarshaller.finish();
                in.readerIndex(in.readerIndex() + objectSize);
                return obj;
            } finally {
                unmarshaller.close();
            }
        }
    }
}
